/*
 * Copyright (c) ycq_work@163.com
 */

package com.mxg.blog.common;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @Author: ycq_work@163.com
 * @DATE: 2023/5/9 13:36
 * 日志
 */
public class CqLogger {
    // 设置日志等级
    private static final String[] sLevel = {"DEBUG", "INFO", "WARN", "ERROR", "FATAL"};
    // 文件名称
    private String mFilename;
    // 写入文件名称
    private FileWriter mFileWriter;
    private Level mLevel;
    //文件内最大行数
    private int mMaxSize;
    // 行数最长
    private int mLength;

    private static CqLogger instance;

    public enum Level {
        DEBUG, INFO, WARN, ERROR, FATAL
    }

    private CqLogger() {
        this.mLevel = Level.DEBUG;
    }

    // 创建类
    public static synchronized CqLogger getInstance() {
        if (instance == null) {
            instance = new CqLogger();
        }
        return instance;
    }

    // 打开文件
    public void open(String filename) throws IOException {
        // 文件命名
        this.mFilename = filename;
        // 打开文件
        File file = new File(filename);

        if (!file.exists()) {
            file.createNewFile();
        }
        this.mFileWriter = new FileWriter(file, true);
    }

    public void close() throws IOException {
        this.mFileWriter.close();
    }

    public void logPrint(Level level, String file, int line, String format, Object... args) {
        if (this.mLevel.ordinal() > level.ordinal()) {
            return;
        }
        if (this.mFileWriter == null) {
            System.out.println("Error: file writer is null");
            return;
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String timestamp = sdf.format(new Date());
        String levelStr = sLevel[level.ordinal()];
        String content = String.format("%s %s %s : %d ", timestamp, levelStr, file, line);
        content += String.format(format, args);
        content += System.lineSeparator();
        try {
            mFileWriter.write(content);
            mFileWriter.flush();
            mLength += content.length();
            if (mMaxSize > 0 && mLength >= mMaxSize) {
                rotate();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void setLevel(Level level) {
        this.mLevel = level;
    }

    public void setMaxSize(int maxSize) {
        this.mMaxSize = maxSize;
    }

    private void rotate() throws IOException {
        close();
        File oldFile = new File(mFilename);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String timestamp = sdf.format(new Date());
        String newName = mFilename + "_" + timestamp;
        File newFile = new File(newName);
        if (!oldFile.renameTo(newFile)) {
            throw new IOException("rename log file failed.");
        }
        open(mFilename);
    }

    // 打印日志到控制台
    public void print(String format, Object... args)
    {
        // 获取打印位置的代码行数
        int line = getCurrentLineNum();
        // 获取类名
        String className = getCurrentClassName();
        // 获取函数名
        String methodName = getCurrentMethodName();
        // 设置时间格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 获取时间戳
        String timestamp = sdf.format(new Date());
        // 设置日志等级
        String levelStr = CqLogger.sLevel[0];
        // 设置日志打印格式
        String content = String.format("\u001B[36m%s \u001B[0m", "MXGLogger");
        content += String.format("\u001B[0m%s  %s \u001B[0m", timestamp, levelStr);
        content += String.format("\u001B[32m%d --- \u001B[0m", line);
        content += String.format("\u001B[0m[ %20s ] %-50s : \u001B[0m", methodName, className);
        content += String.format("\u001B[33m%s \u001B[224;164;96m", String.format(format + args));
        content += System.lineSeparator();
        System.out.print(content);
    }

    // 重构 内容遇上一个一致
    public void print(Object format)
    {
        // 获取打印位置的代码行数
        int line = getCurrentLineNum();
        // 获取类名
        String className = getCurrentClassName();
        // 获取函数名
        String methodName = getCurrentMethodName();
        // 设置时间格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 获取时间戳
        String timestamp = sdf.format(new Date());
        // 设置日志等级
        String levelStr = CqLogger.sLevel[0];
        //String levelStr = "测试";
        // 设置日志打印格式
        String content = String.format("\u001B[36m%s \u001B[0m", "MXGLogger");
        content += String.format("\u001B[0m%s  \u001B[38;5;69m%s \u001B[0m", timestamp, levelStr);
        content += String.format("\u001B[32m%d \u001B[0m--- \u001B[0m", line);
        content += String.format("\u001B[0m[ %20s ] %-50s : \u001B[0m", methodName, className);
        content += String.format("\u001B[224;164;96m%s \u001B[0m", format);
        content += System.lineSeparator();
        System.out.print(content);
    }

    // 获取调用堆栈的信息调用
    // 因此在调用过程中, 方法本身会占用堆栈, 还有getCurrent等, 以及打印函数, 因此设置 int CALL_STACK_INDEX = 3 (勿动)
    // 代码复用程度低, 日后优化
    private static int getCurrentLineNum() {
        final int CALL_STACK_INDEX = 3;
        // 调用堆栈
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        // 获取当前堆栈
        StackTraceElement traceElement = stackTrace[CALL_STACK_INDEX];
        // 返回当前行
        return traceElement.getLineNumber();
    }

    private static String getCurrentClassName() {
        final int CALL_STACK_INDEX = 3;
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        StackTraceElement traceElement = stackTrace[CALL_STACK_INDEX];
        String className = traceElement.getClassName();
        try {
            Class<?> clazz = Class.forName(className);
            return clazz.getName();
        } catch (ClassNotFoundException | SecurityException e) {
            e.printStackTrace();
        }
        return "";
    }

    private static String getCurrentMethodName() {
        final int CALL_STACK_INDEX = 3;
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        StackTraceElement traceElement = stackTrace[CALL_STACK_INDEX];
        String className = traceElement.getClassName();
        try {
            Class<?> clazz = Class.forName(className);
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                if (method.getName().equals(traceElement.getMethodName())) {
                    return method.getName();
                }
            }
        } catch (ClassNotFoundException | SecurityException e) {
            e.printStackTrace();
        }
        return "";
    }
}
